﻿using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using JTActiveSafety.Protocol;

namespace JTActiveSafety.Gateway
{
    /// <summary>
    /// Simple helper class on <see cref="SequenceReader{T}"/>.
    /// I expect that a more efficient/less buggy version of these functions will be part of SequenceReader API someday.
    /// </summary>
    public static class SequenceReaderExtensions
    {
        /// <summary>
        /// 读取一个完整的808包
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="span"></param>
        /// <returns></returns>
        public static bool TryRead808Package(ref this SequenceReader<byte> reader, [NotNullWhen(true)] out ReadOnlySpan<byte> span)
        {
            byte flag = 126; //0x7e

            if (reader.Length < 14) // 808包最小也得有14位
            {
                goto End;
            }

            if (reader.IsNext(flag, true))
            {
                if (reader.TryReadTo(out ReadOnlySpan<byte> _span2, flag))
                {
                    reader.Rewind(_span2.Length + 2);
                    if (reader.TryReadSubSpan(_span2.Length + 2, out ReadOnlySpan<byte> _span3))
                    {
                        span = _span3;
                        return true;
                    }
                }
                reader.Rewind(1);
            }
        End:
            span = default;
            return false;
        }

        /// <summary>
        /// 读取一个完整的主动安全附件包
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="span"></param>
        /// <returns>没读取成功一般情况是长度不够，小概率是错误包或无效数据包</returns>
        public static bool TryReadActiveSafetyPackage(ref this SequenceReader<byte> reader, [NotNullWhen(true)] out ReadOnlySpan<byte> span)
        {
            if (reader.Remaining < 62)//主动安全附件文件包最小长度 4+50+4+4 = 62
            {
                goto End;
            }
            if (reader.TryReadBigEndian(out int ss)) //+4
            {

                if (ss == 808543076 /*0x30 31 63 64*/)
                {
                    reader.Advance(54); // + 54
                    if (reader.TryReadSubSpan(4, out ReadOnlySpan<byte> _len)) //+62
                    {
                        reader.Rewind(62); //-62
                        var length = 62 + BinaryPrimitives.ReadUInt32BigEndian(_len);

                        if (reader.Remaining < length)//长度不够
                        {
                            goto End;
                        }
                        else if (reader.TryReadSubSpan((int)length, out ReadOnlySpan<byte> _span2))
                        {
                            span = _span2;
                            return true;
                        }
                    }
                }
                reader.Rewind(4);
            }

        End:
            span = default;
            return false;
        }

        /// <summary>
        /// Copy and Paste of https://github.com/dotnet/runtime/issues/29318#issuecomment-484987895
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="length"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool TryReadSubSpan(ref this SequenceReader<byte> reader, int length, [NotNullWhen(true)] out ReadOnlySpan<byte> value)
        {
            ReadOnlySpan<byte> span = reader.UnreadSpan;
            if (span.Length < length)
                return TryReadMultisegmentSpan(ref reader, length, out value);

            value = span.Slice(0, length);
            reader.Advance(length);
            return true;
        }

        /// <summary>
        /// Copy and Paste of https://github.com/dotnet/runtime/issues/29318#issuecomment-484987895
        /// </summary>
        static bool TryReadMultisegmentSpan(ref SequenceReader<byte> reader, int length, [NotNullWhen(true)] out ReadOnlySpan<byte> value)
        {
            Debug.Assert(reader.UnreadSpan.Length < length);

            // Not enough data in the current segment, try to peek for the data we need.
            // In my use case, these strings cannot be more than 64kb, so stack memory is fine.
            Span<byte> buffer = stackalloc byte[length];
            if (!reader.TryCopyTo(buffer))
            {
                value = default;
                return false;
            }
            value = buffer.ToArray();
            reader.Advance(length);
            return true;
        }

        /// <summary>
        /// 官方为什么不实现这个重载  https://docs.microsoft.com/zh-cn/dotnet/api/system.buffers.sequencereader-1?view=netstandard-2.1 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sequenceReader"></param>
        /// <param name="span"></param>
        /// <param name="delimiter"></param>
        /// <param name="advancePastDelimiter"></param>
        /// <returns></returns>
        public static bool TryReadTo<T>(this ref SequenceReader<T> sequenceReader, out ReadOnlySpan<T> span, ReadOnlySpan<T> delimiter, bool advancePastDelimiter = true) where T : unmanaged, IEquatable<T>
        {
            if (sequenceReader.TryReadTo(out ReadOnlySequence<T> sequence, delimiter, advancePastDelimiter))
            {
                span = sequence.IsSingleSegment ? sequence.FirstSpan : sequence.ToArray();
                return true;
            }
            span = default;
            return false;
        }
    }
}